﻿/*
 MIT License

 Copyright (c) 2020 Agadzhanov Vladimir

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
                                                                                 */

#ifndef QSERIALIZER_H
#define QSERIALIZER_H

/* JSON */
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonValue>

/* XML */
#include <QtXml/QDomDocument>
#include <QtXml/QDomElement>

/* META OBJECT SYSTEM */
#include <QVariant>
#include <QMetaProperty>
#include <QMetaObject>
#include <QMetaType>

#include <type_traits>
#include <QDebug>

#define QS_VERSION "1.2"

/* Generate metaObject method */
#define QS_META_OBJECT_METHOD \
    virtual const QMetaObject * metaObject() const { \
        return &this->staticMetaObject; \
    } \

/* Mark class as serializable */
#define QS_SERIALIZABLE QS_META_OBJECT_METHOD

Q_DECLARE_METATYPE(QDomNode)
Q_DECLARE_METATYPE(QDomElement)
class QSerializer {
    Q_GADGET
    QS_SERIALIZABLE
public:
    QSerializer(){
    }

    ///\brief Convert QJsonValue in QJsonDocument as QByteArray
    static QByteArray toByteArray(const QJsonValue & value){
        return QJsonDocument(value.toObject()).toJson();
    }

    ///\brief Convert QDomNode in QDomDocument as QByteArray
    static QByteArray toByteArray(const QDomNode & value) {
        QDomDocument doc = value.toDocument();
        return doc.toByteArray();
    }

    ///\brief Make xml processing instruction (hat) and returns new XML QDomDocument. On deserialization procedure all processing instructions will be ignored.
    static QDomDocument appendXmlHat(const QDomNode &node, QString encoding, QString version = "1.0"){
        QDomDocument doc = node.toDocument();
        QDomNode xmlNode = doc.createProcessingInstruction("xml", QString("version=\"%1\" encoding=\"%2\"").arg(version).arg(encoding));
        doc.insertBefore(xmlNode, doc.firstChild());
        return doc;
    }


    ///\brief Serialize all accessed JSON propertyes for this object
    QJsonObject toJson() const {
        QJsonObject json;
        for(int i = 0; i < metaObject()->propertyCount(); i++)
        {
            if(QString(metaObject()->property(i).typeName()) != QMetaType::typeName(qMetaTypeId<QJsonValue>()))
                continue;

            json.insert(metaObject()->property(i).name(), metaObject()->property(i).readOnGadget(this).toJsonValue());
        }
        return json;
    }

    ///\brief Deserialize all accessed JSON propertyes for this object
    void fromJson(const QJsonValue & val) {
        if(val.isObject())
        {
            QJsonObject json = val.toObject();
            QStringList keys = json.keys();
            int propCount = metaObject()->propertyCount();
            for(int i = 0; i < propCount; i++)
            {
                if(QString(metaObject()->property(i).typeName()) != QMetaType::typeName(qMetaTypeId<QJsonValue>()))
                    continue;

                for(auto key : json.keys())
                {
                    if(key == metaObject()->property(i).name())
                    {
                        metaObject()->property(i).writeOnGadget(this, json.value(key));
                        break;
                    }
                }
            }
        }
    }

    ///\brief Serialize all accessed XML propertyes for this object
    QDomNode toXml() const {
        QDomDocument doc;
        QDomElement el = doc.createElement(metaObject()->className());
        for(int i = 0; i < metaObject()->propertyCount(); i++)
        {
            if(QString(metaObject()->property(i).typeName()) != QMetaType::typeName(qMetaTypeId<QDomNode>()))
                continue;

            el.appendChild(QDomNode(metaObject()->property(i).readOnGadget(this).value<QDomNode>()));
        }
        doc.appendChild(el);
        return doc;
    }

    ///\brief Deserialize all accessed XML propertyes for this object
    void fromXml(const QDomNode & val){
        QDomNode doc = val;

        if(doc.firstChild().isProcessingInstruction())
               doc.removeChild(doc.firstChild());

        if(doc.firstChild().nodeName() == metaObject()->className())
        {
            for(int i = 0; i < metaObject()->propertyCount(); i++)
            {
                if(QString(metaObject()->property(i).typeName()) != QMetaType::typeName(qMetaTypeId<QDomNode>()))
                    continue;

                QDomNode fieldNode = doc.firstChild().firstChild();
                while(!fieldNode.isNull())
                {
                    QDomElement currentElement = fieldNode.toElement();

                    QDomElement tmp = metaObject()->property(i).readOnGadget(this).value<QDomNode>().firstChildElement();

                    if(metaObject()->property(i).name() == currentElement.tagName() || tmp.tagName() == currentElement.tagName())
                    {
                        metaObject()->property(i).writeOnGadget(this, QVariant::fromValue<QDomNode>(currentElement));
                        break;
                    }
                    fieldNode = fieldNode.nextSibling();
                }
            }
        }
        else if (metaObject()->className() == doc.toElement().tagName())
        {
            for(int i = 0; i < metaObject()->propertyCount(); i++)
            {
                if(QString(metaObject()->property(i).typeName()) != QMetaType::typeName(qMetaTypeId<QDomNode>()))
                    continue;

                QDomNode fieldNode = doc.firstChild();
                while(!fieldNode.isNull())
                {
                    QDomElement currentElement = fieldNode.toElement();

                    QDomElement tmp = metaObject()->property(i).readOnGadget(this).value<QDomNode>().firstChildElement();

                    if(metaObject()->property(i).name() == currentElement.tagName() || tmp.tagName() == currentElement.tagName())
                    {
                        metaObject()->property(i).writeOnGadget(this, QVariant::fromValue<QDomNode>(currentElement));
                        break;
                    }
                    fieldNode = fieldNode.nextSibling();
                }
            }
        }
    }

    void fromXml(const QByteArray & data) {
        QDomDocument d;
        d.setContent(data);
        fromXml(d);
    }

    void fromJson(const QByteArray & data) {
        fromJson(QJsonDocument::fromJson(data).object());
    }
};

#define GET(prefix, name) get_##prefix##_##name
#define SET(prefix, name) set_##prefix##_##name


/* Create variable */
#define QS_DECLARE_MEMBER(type, name)                                                       \
    public :                                                                                \
    type name = type();                                                                     \

/* Create JSON property and methods for primitive type field*/
#define QS_JSON_FIELD(type, name)                                                           \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json, name))                  \
    private:                                                                                \
        QJsonValue GET(json, name)() const {                                                \
            QJsonValue val = QJsonValue::fromVariant(QVariant(name));                       \
            return val;                                                                     \
        }                                                                                   \
        void SET(json, name)(const QJsonValue & varname){                                   \
            name = varname.toVariant().value<type>();                                       \
        }                                                                                   \


/* Create XML property and methods for primitive type field*/
#define QS_XML_FIELD(type, name)                                                            \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                      \
    private:                                                                                \
    QDomNode GET(xml, name)() const {                                                       \
        QDomDocument doc;                                                                   \
        QString strname = #name;                                                            \
        QDomElement element = doc.createElement(strname);                                   \
        QDomText valueOfProp = doc.createTextNode(QVariant(name).toString());               \
        element.appendChild(valueOfProp);                                                   \
        doc.appendChild(element);                                                           \
        return  QDomNode(element);                                                          \
    }                                                                                       \
    void SET(xml, name)(const QDomNode &node) {                                             \
        if(!node.isNull() && node.isElement()){                                             \
            QDomElement domElement = node.toElement();                                      \
            if(domElement.tagName() == #name)                                               \
                name = QVariant(domElement.text()).value<type>();                           \
        }                                                                                   \
    }                                                                                       \


/* Generate JSON-property and methods for primitive type objects */
/* This collection must be provide method append(T) (it's can be QList, QVector)    */
#define QS_JSON_ARRAY(itemType, name)                                                       \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json, name))                  \
    private:                                                                                \
        QJsonValue GET(json, name)() const {                                                \
            QJsonArray val;                                                                 \
            for(int i = 0; i < name.size(); i++)                                            \
                val.push_back(name.at(i));                                                  \
            return QJsonValue::fromVariant(val);                                            \
        }                                                                                   \
        void SET(json, name)(const QJsonValue & varname) {                                  \
            if(!varname.isArray())                                                          \
                return;                                                                     \
            name.clear();                                                                   \
            QJsonArray val = varname.toArray();                                             \
            for(auto item : val) {                                                          \
                itemType tmp;                                                               \
                tmp = item.toVariant().value<itemType>();                                   \
                name.append(tmp);                                                           \
            }                                                                               \
        }                                                                                   \

/* Generate XML-property and methods for primitive type objects */
/* This collection must be provide method append(T) (it's can be QList, QVector)    */
#define QS_XML_ARRAY(itemType, name)                                                        \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                      \
    private:                                                                                \
        QDomNode GET(xml, name)() const {                                                   \
            QDomDocument doc;                                                               \
            QString strname = #name;                                                        \
            QDomElement arrayXml = doc.createElement(QString(strname));                     \
            arrayXml.setAttribute("type", "array");                                         \
                                                                                            \
            for(int i = 0; i < name.size(); i++) {                                          \
                itemType item = name.at(i);                                                 \
                QDomElement itemXml = doc.createElement("item");                            \
                itemXml.setAttribute("type", #itemType);                                    \
                itemXml.setAttribute("index", i);                                           \
                itemXml.appendChild(doc.createTextNode(QVariant(item).toString()));         \
                arrayXml.appendChild(itemXml);                                              \
            }                                                                               \
                                                                                            \
            doc.appendChild(arrayXml);                                                      \
            return  QDomNode(doc);                                                          \
        }                                                                                   \
        void SET(xml, name)(const QDomNode & node) {                                        \
            QDomNode domNode = node.firstChild();                                           \
            name.clear();                                                                   \
            while(!domNode.isNull()) {                                                      \
                if(domNode.isElement()) {                                                   \
                    QDomElement domElement = domNode.toElement();                           \
                    name.append(QVariant(domElement.text()).value<itemType>());             \
                }                                                                           \
                domNode = domNode.nextSibling();                                            \
            }                                                                               \
        }                                                                                   \





/* Generate JSON-property and methods for some custom class */
/* Custom type must be provide methods fromJson and toJson or inherit from QSerializer */
#define QS_JSON_OBJECT(type, name)                                                          \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json, name))                  \
    private:                                                                                \
    QJsonValue GET(json, name)() const {                                                    \
        QJsonObject val = name.toJson();                                                    \
        return QJsonValue(val);                                                             \
    }                                                                                       \
    void SET(json, name)(const QJsonValue & varname) {                                      \
        if(!varname.isObject())                                                             \
        return;                                                                             \
        name.fromJson(varname);                                                             \
    }                                                                                       \

/* Generate XML-property and methods for some custom class */
/* Custom type must be provide methods fromJson and toJson or inherit from QSerializer */
#define QS_XML_OBJECT(type, name)                                                           \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                      \
    private:                                                                                \
        QDomNode GET(xml, name)() const {                                                   \
            return name.toXml();                                                            \
        }                                                                                   \
        void SET(xml, name)(const QDomNode & node){                                         \
            name.fromXml(node);                                                             \
        }                                                                                   \

/* Generate JSON-property and methods for collection of custom type objects */
/* Custom item type must be provide methods fromJson and toJson or inherit from QSerializer */
/* This collection must be provide method append(T) (it's can be QList, QVector)    */
#define QS_JSON_ARRAY_OBJECTS(itemType, name)                                               \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json, name))                  \
    private:                                                                                \
        QJsonValue GET(json, name)() const {                                                \
            QJsonArray val;                                                                 \
            for(int i = 0; i < name.size(); i++)                                            \
                val.push_back(name.at(i).toJson());                                         \
            return QJsonValue::fromVariant(val);                                            \
        }                                                                                   \
        void SET(json, name)(const QJsonValue & varname) {                                  \
            if(!varname.isArray())                                                          \
                return;                                                                     \
            name.clear();                                                                   \
            QJsonArray val = varname.toArray();                                             \
            for(int i = 0; i < val.size(); i++) {                                           \
                itemType tmp;                                                               \
                tmp.fromJson(val.at(i));                                                    \
                name.append(tmp);                                                           \
            }                                                                               \
        }                                                                                   \

/* Generate XML-property and methods for collection of custom type objects  */
/* Custom type must be provide methods fromXml and toXml or inherit from QSerializer */
/* This collection must be provide method append(T) (it's can be QList, QVector)    */
#define QS_XML_ARRAY_OBJECTS(itemType, name)                                                \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                      \
    private:                                                                                \
    QDomNode GET(xml, name)() const {                                                       \
        QDomDocument doc;                                                                   \
        QDomElement element = doc.createElement(#name);                                     \
        element.setAttribute("type", "array");                                              \
        for(int i = 0; i < name.size(); i++)                                                \
            element.appendChild(name.at(i).toXml());                                        \
        doc.appendChild(element);                                                           \
        return QDomNode(doc);                                                               \
    }                                                                                       \
    void SET(xml, name)(const QDomNode & node) {                                            \
        name.clear();                                                                       \
        QDomNodeList nodesList = node.childNodes();                                         \
        for(int i = 0;  i < nodesList.size(); i++) {                                        \
            itemType tmp;                                                                   \
            tmp.fromXml(nodesList.at(i));                                                   \
            name.append(tmp);                                                               \
        }                                                                                   \
    }                                                                                       \

/* Generate JSON-property and methods for dictionary of simple fields (int, bool, QString, ...)  */
/* Custom type must be inherit from QSerializer */
/* This collection must be provide method insert(KeyT, ValueT) (it's can be QMap, QHash)    */
/* THIS IS FOR QT DICTIONARY TYPES, for example QMap<int, QString>, QMap<int,int>, ...*/
#define QS_JSON_QT_DICT(map, name)                                                          \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json,name))                   \
    private:                                                                                \
    QJsonValue GET(json, name)() const {                                                    \
        QJsonObject val;                                                                    \
        for(auto p = name.constBegin(); p != name.constEnd(); ++p) {                        \
            val.insert(                                                                     \
                QVariant(p.key()).toString(),                                               \
                QJsonValue::fromVariant(QVariant(p.value())));                              \
        }                                                                                   \
        return val;                                                                         \
    }                                                                                       \
    void SET(json, name)(const QJsonValue & varname) {                                      \
        QJsonObject val = varname.toObject();                                               \
        name.clear();                                                                       \
        for(auto p = val.constBegin() ;p != val.constEnd(); ++p) {                          \
            name.insert(                                                                    \
                QVariant(p.key()).value<map::key_type>(),                                   \
                QVariant(p.value()).value<map::mapped_type>());                             \
        }                                                                                   \
    }                                                                                       \

/* Generate XML-property and methods for dictionary of simple fields (int, bool, QString, ...)  */
/* Custom type must be inherit from QSerializer */
/* This collection must be provide method insert(KeyT, ValueT) (it's can be QMap, QHash)    */
/* THIS IS FOR QT DICTIONARY TYPES, for example QMap<int, QString>, QMap<int,int>, ...*/
#define QS_XML_QT_DICT(map, name)                                                                       \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                                  \
    private:                                                                                            \
    QDomNode GET(xml, name)() const {                                                                   \
        QDomDocument doc;                                                                               \
        QDomElement element = doc.createElement(#name);                                                 \
        element.setAttribute("type", "map");                                                            \
        for(auto p = name.begin(); p != name.end(); ++p)                                                \
        {                                                                                               \
            QDomElement e = doc.createElement("item");                                                  \
            e.setAttribute("key", QVariant(p.key()).toString());                                        \
            e.setAttribute("value", QVariant(p.value()).toString());                                    \
            element.appendChild(e);                                                                     \
        }                                                                                               \
        doc.appendChild(element);                                                                       \
        return QDomNode(doc);                                                                           \
    }                                                                                                   \
    void SET(xml, name)(const QDomNode & node) {                                                        \
        if(!node.isNull() && node.isElement())                                                          \
        {                                                                                               \
            QDomElement root = node.toElement();                                                        \
            if(root.tagName() == #name)                                                                 \
            {                                                                                           \
                QDomNodeList childs = root.childNodes();                                                \
                                                                                                        \
                for(int i = 0; i < childs.size(); ++i) {                                                \
                QDomElement item = childs.at(i).toElement();                                            \
                name.insert(QVariant(item.attributeNode("key").value()).value<map::key_type>(),         \
                            QVariant(item.attributeNode("value").value()).value<map::mapped_type>());   \
                }                                                                                       \
            }                                                                                           \
        }                                                                                               \
    }                                                                                                   \


/* Generate JSON-property and methods for dictionary of custom type objects  */
/* Custom type must be inherit from QSerializer */
/* This collection must be provide method inserv(KeyT, ValueT) (it's can be QMap, QHash)    */
/* THIS IS FOR QT DICTIONARY TYPES, for example QMap<int, CustomSerializableType> */
#define QS_JSON_QT_DICT_OBJECTS(map, name)                                                  \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json,name))                   \
    private:                                                                                \
    QJsonValue GET(json, name)() const {                                                    \
        QJsonObject val;                                                                    \
        for(auto p = name.begin(); p != name.end(); ++p) {                                  \
            val.insert(                                                                     \
                QVariant::fromValue(p.key()).toString(),                                    \
                p.value().toJson());                                                        \
        }                                                                                   \
        return val;                                                                         \
    }                                                                                       \
    void SET(json, name)(const QJsonValue & varname) {                                      \
        QJsonObject val = varname.toObject();                                               \
        name.clear();                                                                       \
        for(auto p = val.constBegin();p != val.constEnd(); ++p) {                           \
        map::mapped_type tmp;                                                               \
        tmp.fromJson(p.value());                                                            \
        name.insert(                                                                        \
                QVariant(p.key()).value<map::key_type>(),                                   \
                tmp);                                                                       \
        }                                                                                   \
    }                                                                                       \


/* Generate XML-property and methods for dictionary of custom type objects  */
/* Custom type must be inherit from QSerializer */
/* This collection must be provide method insert(KeyT, ValueT) (it's can be QMap, QHash)    */
/* THIS IS FOR QT DICTIONARY TYPES, for example QMap<int, CustomSerializableType> */
#define QS_XML_QT_DICT_OBJECTS(map, name)                                                               \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                                  \
    private:                                                                                            \
    QDomNode GET(xml, name)() const {                                                                   \
        QDomDocument doc;                                                                               \
        QDomElement element = doc.createElement(#name);                                                 \
        element.setAttribute("type", "map");                                                            \
        for(auto p = name.begin(); p != name.end(); ++p)                                                \
        {                                                                                               \
            QDomElement e = doc.createElement("item");                                                  \
            e.setAttribute("key", QVariant(p.key()).toString());                                        \
            e.appendChild(p.value().toXml());                                                           \
            element.appendChild(e);                                                                     \
        }                                                                                               \
        doc.appendChild(element);                                                                       \
        return QDomNode(doc);                                                                           \
    }                                                                                                   \
    void SET(xml, name)(const QDomNode & node) {                                                        \
        if(!node.isNull() && node.isElement())                                                          \
        {                                                                                               \
            QDomElement root = node.toElement();                                                        \
            if(root.tagName() == #name)                                                                 \
            {                                                                                           \
                QDomNodeList childs = root.childNodes();                                                \
                                                                                                        \
                for(int i = 0; i < childs.size(); ++i) {                                                \
                QDomElement item = childs.at(i).toElement();                                            \
                map::mapped_type tmp;                                                                   \
                tmp.fromXml(item.firstChild());                                                         \
                name.insert(QVariant(item.attributeNode("key").value()).value<map::key_type>(),         \
                            tmp);                                                                       \
                }                                                                                       \
            }                                                                                           \
        }                                                                                               \
    }                                                                                                   \


/* Generate JSON-property and methods for dictionary of simple fields (int, bool, QString, ...)  */
/* Custom type must be inherit from QSerializer */
/* This collection must be provide method insert(KeyT, ValueT) (it's can be std::map)    */
/* THIS IS FOR STL DICTIONARY TYPES, for example std::map<int, QString>, std::map<int,int>, ...*/
#define QS_JSON_STL_DICT(map, name)                                                         \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json,name))                   \
    private:                                                                                \
    QJsonValue GET(json, name)() const {                                                    \
        QJsonObject val;                                                                    \
        for(auto p : name){                                                                 \
            val.insert(                                                                     \
                QVariant::fromValue(p.first).toString(),                                    \
                QJsonValue::fromVariant(QVariant(p.second)));                               \
        }                                                                                   \
        return val;                                                                         \
    }                                                                                       \
    void SET(json, name)(const QJsonValue & varname) {                                      \
        QJsonObject val = varname.toObject();                                               \
        name.clear();                                                                       \
        for(auto p = val.constBegin();p != val.constEnd(); ++p) {                           \
            name.insert(std::pair<map::key_type, map::mapped_type>(                         \
                QVariant(p.key()).value<map::key_type>(),                                   \
                QVariant(p.value()).value<map::mapped_type>()));                            \
        }                                                                                   \
    }                                                                                       \


#define QS_XML_STL_DICT(map, name)                                                                       \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                                  \
    private:                                                                                            \
    QDomNode GET(xml, name)() const {                                                                   \
        QDomDocument doc;                                                                               \
        QDomElement element = doc.createElement(#name);                                                 \
        element.setAttribute("type", "map");                                                            \
        for(auto p : name)                                                                              \
        {                                                                                               \
            QDomElement e = doc.createElement("item");                                                  \
            e.setAttribute("key", QVariant(p.first).toString());                                        \
            e.setAttribute("value", QVariant(p.second).toString());                                     \
            element.appendChild(e);                                                                     \
        }                                                                                               \
        doc.appendChild(element);                                                                       \
        return QDomNode(doc);                                                                           \
    }                                                                                                   \
    void SET(xml, name)(const QDomNode & node) {                                                        \
        if(!node.isNull() && node.isElement())                                                          \
        {                                                                                               \
            QDomElement root = node.toElement();                                                        \
            if(root.tagName() == #name)                                                                 \
            {                                                                                           \
                QDomNodeList childs = root.childNodes();                                                \
                                                                                                        \
                for(int i = 0; i < childs.size(); ++i) {                                                \
                QDomElement item = childs.at(i).toElement();                                            \
                name.insert(std::pair<map::key_type, map::mapped_type>(                                 \
                            QVariant(item.attributeNode("key").value()).value<map::key_type>(),         \
                            QVariant(item.attributeNode("value").value()).value<map::mapped_type>()));  \
                }                                                                                       \
            }                                                                                           \
        }                                                                                               \
    }                                                                                                   \


/* Generate JSON-property and methods for dictionary of custom type objects */
/* Custom type must be inherit from QSerializer */
/* This collection must be provide method insert(KeyT, ValueT) (it's can be std::map)    */
/* THIS IS FOR STL DICTIONARY TYPES, for example std::map<int, CustomSerializableType> */
#define QS_JSON_STL_DICT_OBJECTS(map, name)                                                 \
    Q_PROPERTY(QJsonValue name READ GET(json, name) WRITE SET(json,name))                   \
    private:                                                                                \
    QJsonValue GET(json, name)() const {                                                    \
        QJsonObject val;                                                                    \
        for(auto p : name){                                                                 \
            val.insert(                                                                     \
                QVariant::fromValue(p.first).toString(),                                    \
                p.second.toJson());                                                         \
        }                                                                                   \
        return val;                                                                         \
    }                                                                                       \
    void SET(json, name)(const QJsonValue & varname) {                                      \
        QJsonObject val = varname.toObject();                                               \
        name.clear();                                                                       \
        for(auto p = val.constBegin(); p != val.constEnd(); ++p) {                          \
            map::mapped_type tmp;                                                           \
            tmp.fromJson(p.value());                                                        \
            name.insert(std::pair<map::key_type, map::mapped_type>(                         \
                QVariant(p.key()).value<map::key_type>(),                                   \
                tmp));                                                                      \
        }                                                                                   \
    }                                                                                       \

/* Generate XML-property and methods for dictionary of custom type objects */
/* Custom type must be inherit from QSerializer */
/* This collection must be provide method insert(KeyT, ValueT) (it's can be std::map)    */
/* THIS IS FOR STL DICTIONARY TYPES, for example std::map<int, CustomSerializableType> */
#define QS_XML_STL_DICT_OBJECTS(map, name)                                                              \
    Q_PROPERTY(QDomNode name READ GET(xml, name) WRITE SET(xml, name))                                  \
    private:                                                                                            \
    QDomNode GET(xml, name)() const {                                                                   \
        QDomDocument doc;                                                                               \
        QDomElement element = doc.createElement(#name);                                                 \
        element.setAttribute("type", "map");                                                            \
        for(auto p : name)                                                \
        {                                                                                               \
            QDomElement e = doc.createElement("item");                                                  \
            e.setAttribute("key", QVariant(p.first).toString());                                        \
            e.appendChild(p.second.toXml());                                                            \
            element.appendChild(e);                                                                     \
        }                                                                                               \
        doc.appendChild(element);                                                                       \
        return QDomNode(doc);                                                                           \
    }                                                                                                   \
    void SET(xml, name)(const QDomNode & node) {                                                        \
        if(!node.isNull() && node.isElement())                                                          \
        {                                                                                               \
            QDomElement root = node.toElement();                                                        \
            if(root.tagName() == #name)                                                                 \
            {                                                                                           \
                QDomNodeList childs = root.childNodes();                                                \
                                                                                                        \
                for(int i = 0; i < childs.size(); ++i) {                                                \
                QDomElement item = childs.at(i).toElement();                                            \
                map::mapped_type tmp;                                                                   \
                tmp.fromXml(item.firstChild());                                                         \
                name.insert(std::pair<map::key_type, map::mapped_type> (                                \
                            QVariant(item.attributeNode("key").value()).value<map::key_type>(),         \
                            tmp));                                                                      \
                }                                                                                       \
            }                                                                                           \
        }                                                                                               \
    }                                                                                                   \





/* BIND: */
/* generate serializable propertyes JSON and XML for primitive type field */
#define QS_BIND_FIELD(type, name)                                                           \
    QS_JSON_FIELD(type, name)                                                               \
    QS_XML_FIELD(type, name)                                                                \

/* BIND: */
/* generate serializable propertyes JSON and XML for collection of primitive type fields */
#define QS_BIND_COLLECTION(itemType, name)                                                  \
    QS_JSON_ARRAY(itemType, name)                                                           \
    QS_XML_ARRAY(itemType, name)                                                            \

/* BIND: */
/* generate serializable propertyes JSON and XML for custom type object */
#define QS_BIND_OBJECT(type, name)                                                          \
    QS_JSON_OBJECT(type, name)                                                              \
    QS_XML_OBJECT(type, name)                                                               \

/* BIND: */
/* generate serializable propertyes JSON and XML for collection of custom type objects */
#define QS_BIND_COLLECTION_OBJECTS(itemType, name)                                          \
    QS_JSON_ARRAY_OBJECTS(itemType, name)                                                   \
    QS_XML_ARRAY_OBJECTS(itemType, name)                                                    \

/* BIND: */
/* generate serializable propertyes JSON and XML for dictionary with primitive value type for QT DICTIONARY TYPES */
#define QS_BIND_QT_DICT(map, name)                                                         \
    QS_JSON_QT_DICT(map, name)                                                             \
    QS_XML_QT_DICT(map, name)                                                              \

/* BIND: */
/* generate serializable propertyes JSON and XML for dictionary of custom type objects for QT DICTIONARY TYPES */
#define QS_BIND_QT_DICT_OBJECTS(map, name)                                                 \
    QS_JSON_QT_DICT_OBJECTS(map, name)                                                     \
    QS_XML_QT_DICT_OBJECTS(map,name)                                                       \


/* BIND: */
/* generate serializable propertyes JSON and XML for dictionary with primitive value type for STL DICTIONARY TYPES */
#define QS_BIND_STL_DICT(map, name)                                                         \
    QS_JSON_STL_DICT(map, name)                                                             \
    QS_XML_STL_DICT(map, name)                                                              \


/* BIND: */
/* generate serializable propertyes JSON and XML for dictionary of custom type objects for STL DICTIONARY TYPES */
#define QS_BIND_STL_DICT_OBJECTS(map, name)                                                 \
    QS_JSON_STL_DICT_OBJECTS(map, name)                                                     \
    QS_XML_STL_DICT_OBJECTS(map,name)                                                       \



/* CREATE AND BIND: */
/* Make primitive field and generate serializable propertyes */
/* For example: QS_FIELD(int, digit), QS_FIELD(bool, flag) */
#define QS_FIELD(type, name)                                                                \
    QS_DECLARE_MEMBER(type, name)                                                           \
    QS_BIND_FIELD(type, name)                                                               \

/* CREATE AND BIND: */
/* Make collection of primitive type objects [collectionType<itemType> name] and generate serializable propertyes for this collection */
/* This collection must be provide method append(T) (it's can be QList, QVector)    */
#define QS_COLLECTION(collectionType, itemType, name)                                       \
    QS_DECLARE_MEMBER(collectionType<itemType>, name)                                       \
    QS_BIND_COLLECTION(itemType, name)                                                      \

/* CREATE AND BIND: */
/* Make custom class object and bind serializable propertyes */
/* This class must be inherited from QSerializer */
#define QS_OBJECT(type,name)                                                                \
    QS_DECLARE_MEMBER(type, name)                                                           \
    QS_BIND_OBJECT(type, name)                                                              \

/* CREATE AND BIND: */
/* Make collection of custom class objects [collectionType<itemType> name] and bind serializable propertyes */
/* This collection must be provide method append(T) (it's can be QList, QVector)    */
#define QS_COLLECTION_OBJECTS(collectionType, itemType, name)                               \
    QS_DECLARE_MEMBER(collectionType<itemType>, name)                                       \
    QS_BIND_COLLECTION_OBJECTS(itemType, name)                                              \


/* CREATE AND BIND: */
/* Make dictionary collection of simple types [dictionary<key, itemType> name] and bind serializable propertyes */
/* This collection must be QT DICTIONARY TYPE */
#define QS_QT_DICT(map, first, second, name)                                                \
    public:                                                                                 \
    typedef map<first,second> dict_##name##_t;                                              \
    dict_##name##_t name = dict_##name##_t();                                               \
    QS_BIND_QT_DICT(dict_##name##_t, name)                                                  \

/* CREATE AND BIND: */
/* Make dictionary collection of custom class objects [dictionary<key, itemType> name] and bind serializable propertyes */
/* This collection must be QT DICTIONARY TYPE */
#define QS_QT_DICT_OBJECTS(map, first, second, name)                                        \
    public:                                                                                 \
    typedef map<first,second> dict_##name##_t;                                              \
    dict_##name##_t name = dict_##name##_t();                                               \
    QS_BIND_QT_DICT_OBJECTS(dict_##name##_t, name)                                          \

/* CREATE AND BIND: */
/* Make dictionary collection of simple types [dictionary<key, itemType> name] and bind serializable propertyes */
/* This collection must be STL DICTIONARY TYPE */
#define QS_STL_DICT(map, first, second, name)                                               \
    public:                                                                                 \
    typedef map<first,second> dict_##name##_t;                                              \
    dict_##name##_t name = dict_##name##_t();                                               \
    QS_BIND_STL_DICT(dict_##name##_t, name)                                                 \

/* CREATE AND BIND: */
/* Make dictionary collection of custom class objects [dictionary<key, itemType> name] and bind serializable propertyes */
/* This collection must be STL DICTIONARY TYPE */
#define QS_STL_DICT_OBJECTS(map, first, second, name)                                       \
    public:                                                                                 \
    typedef map<first,second> dict_##name##_t;                                              \
    dict_##name##_t name = dict_##name##_t();                                               \
    QS_BIND_STL_DICT_OBJECTS(dict_##name##_t, name)                                         \



#endif // QSERIALIZER_H

